package builds

import (
	"errors"
	"fmt"
	"github.com/liuxd6825/dapr-ddd-cli/pkg/config"
	"github.com/liuxd6825/dapr-ddd-cli/pkg/utils"
	"os"
	"strings"
)

type Build interface {
	Build() error
	GetOutFile() string
	SetOverwrite(overwrite bool)
	GetOverwrite() bool
}

type BaseBuild struct {
	Config     *config.Config
	Aggregate  *config.Aggregate
	TmplFile   string
	OutFile    string
	ValuesFunc func() map[string]interface{}
	builds     []Build
	overwrite  bool
}

type AddBuildOptions struct {
	Overwrite *bool
}

func NewAddBuildOptions(opts ...*AddBuildOptions) *AddBuildOptions {
	o := &AddBuildOptions{}
	for _, item := range opts {
		if item.Overwrite != nil {
			o.Overwrite = item.Overwrite
		}
	}
	return o
}

func NewBaseBuild(config *config.Config, aggregate *config.Aggregate) *BaseBuild {
	res := &BaseBuild{
		Config:    config,
		Aggregate: aggregate,
	}
	res.ValuesFunc = res.Values
	return res
}

func (b *BaseBuild) SetOverwrite(overwrite bool) {
	b.overwrite = overwrite
}

func (b *BaseBuild) GetOverwrite() bool {
	return b.overwrite
}

func (b *BaseBuild) AddBuild(build Build, opts ...*AddBuildOptions) {
	opt := NewAddBuildOptions(opts...)
	if opt.GetOverwrite() != nil {
		build.SetOverwrite(*opt.GetOverwrite())
	}
	b.builds = append(b.builds, build)
}

func (b *BaseBuild) AggregateName() string {
	return utils.FirstUpper(b.Aggregate.Name)
}

func (b *BaseBuild) UpperAggregateName() string {
	return utils.FirstUpper(b.Aggregate.Name)
}

func (b *BaseBuild) LowerAggregateName() string {
	return utils.FirstUpper(b.Aggregate.Name)
}

func (b *BaseBuild) aggregateName() string {
	return utils.FirstLower(b.AggregateName())
}

func (b *BaseBuild) Namespace() string {
	return b.Config.Configuration.GetNamespace()
}

func (b *BaseBuild) Build() (resErr error) {
	defer func() {
		if rec := recover(); rec != nil {
			if err, ok := rec.(error); ok {
				resErr = errors.New("tmpl:" + b.TmplFile + " err:" + err.Error())
			} else if msg, ok := rec.(string); ok {
				resErr = errors.New("tmpl:" + b.TmplFile + " err:" + msg)
			}
		}
	}()
	if b.ValuesFunc != nil {
		return utils.RunTemplate(b.TmplFile, b.ValuesFunc(), b.OutFile, b.overwrite)
	}
	return utils.RunTemplate(b.TmplFile, nil, b.OutFile, b.overwrite)
}

func (b *BaseBuild) Builds() (resErr error) {
	defer func() {
		if rec := recover(); rec != nil {
			if err, ok := rec.(error); ok {
				resErr = err
			} else if msg, ok := rec.(string); ok {
				resErr = errors.New(msg)
			}
		}
	}()
	for _, build := range b.builds {
		if err := build.Build(); err != nil {
			return err
		}
	}
	return nil
}

func (b *BaseBuild) Values() map[string]interface{} {
	values := make(map[string]interface{})
	values["Config"] = b.Config
	values["Configuration"] = b.Config.Configuration
	values["Namespace"] = b.Namespace()
	values["namespace"] = strings.ToLower(b.Namespace())
	values["Aggregates"] = b.Config.Aggregates
	values["ServiceName"] = b.Config.Configuration.ServiceName
	values["CommandServiceName"] = b.Config.Configuration.CommandServiceName()
	values["QueryServiceName"] = b.Config.Configuration.QueryServiceName()
	values["ApiVersion"] = b.Config.Configuration.ApiVersion
	values["K8sNamespace"] = b.Config.Configuration.GetK8sNamespace()
	values["K8sQueryImage"] = b.Config.Configuration.GetK8sQueryImage()
	values["K8sCommandImage"] = b.Config.Configuration.GetK8sCommandImage()
	values["GoMetadata"] = b.Config.Configuration.GoMetadata
	values["JavaMetadata"] = b.Config.Configuration.JavaMetadata
	values["C#Metadata"] = b.Config.Configuration.CSharpMetadata

	if b.Config != nil && b.Config.Configuration != nil {
		values["DefaultViewProperties"] = b.Config.Configuration.DefaultReservedProperties.ViewProperties
		values["DefaultEntityProperties"] = b.Config.Configuration.DefaultReservedProperties.EntityProperties
		values["DefaultValueProperties"] = b.Config.Configuration.DefaultReservedProperties.ValueProperties
		values["DefaultAggregateProperties"] = b.Config.Configuration.DefaultReservedProperties.AggregateProperties
		values["DefaultFieldProperties"] = b.Config.Configuration.DefaultReservedProperties.FieldProperties
	}

	if b.Aggregate != nil {
		aggregateName := utils.SnakeString(b.Aggregate.Name)
		values["Name"] = b.Aggregate.Name
		values["name"] = b.Aggregate.FirstLowerName()
		values["snake_name"] = utils.SnakeString(b.aggregateName())
		values["PluralName"] = b.Aggregate.PluralName()
		values["Aggregate"] = b.Aggregate

		values["AggregateName"] = b.AggregateName()
		values["aggregateName"] = b.aggregateName()
		values["aggregateMidlineName"] = b.Aggregate.MidlineName()
		values["aggregateUrlQueryName"] = fmt.Sprintf("{%v}", b.Aggregate.MidlineName())

		values["AggregatePluralName"] = b.Aggregate.PluralName()
		values["aggregate_name"] = aggregateName
		values["AggregateCommandPackage"] = fmt.Sprintf("%s/command", aggregateName)
		values["AggregateEventPackage"] = fmt.Sprintf("%s/event", aggregateName)
		values["AggregateFieldPackage"] = fmt.Sprintf("%s/field", aggregateName)
		values["AggregateModelPackage"] = fmt.Sprintf("%s/model", aggregateName)
		values["AggregateFactoryPackage"] = fmt.Sprintf("%s/factory", aggregateName)
		values["AggregateServicePackage"] = fmt.Sprintf("%s/service", aggregateName)

		values["Properties"] = b.Aggregate.Properties
		values["Commands"] = b.Aggregate.Commands
		values["Events"] = b.Aggregate.AggregateEvents()
		values["Entities"] = b.Aggregate.Entities
		values["Commands"] = b.Aggregate.Commands

		values["IsEntity"] = false
		values["IsAggregate"] = true
		values["AggregatePath"] = ""
		values["AllViewProperties"] = b.GetAllViewProperties(nil)
		values["Description"] = b.Aggregate.Description

	}

	database := b.Config.Configuration.Database
	values["IsMongo"] = database.Mongo
	values["IsNeo4j"] = database.Neo4j
	values["IsMysql"] = database.MySql

	return values
}

func (b *BaseBuild) ValuesOfCommand(command *config.Command) map[string]interface{} {
	res := b.Values()
	res["Command"] = command

	res["ClassName"] = utils.FirstUpper(command.Name)
	res["className"] = utils.FirstLower(command.Name)

	res["Name"] = command.Name
	res["name"] = utils.FirstLower(command.Name)

	res["Properties"] = command.Properties
	res["Description"] = command.Description

	res["IsHandler"] = command.IsHandler
	res["AggregateId"] = command.AggregateId
	res["Package"] = fmt.Sprintf("%s_command", command.SnakeName())
	res["Action"] = command.Action
	res["IsCreate"] = command.IsCreate()
	res["IsUpdate"] = command.IsUpdate()
	res["IsDelete"] = command.IsDelete()
	res["EventName"] = command.EventName
	res["IsAggregateCreateCommand"] = command.IsAggregateCreateCommand()
	return res
}

func (b *BaseBuild) ValuesOfEntity(entity *config.Entity) map[string]interface{} {
	res := b.Values()
	if entity != nil {
		res["IsEntity"] = true
		res["IsAggregate"] = false
		res["snake_name"] = utils.SnakeString(entity.Name)
		res["name"] = utils.FirstLower(entity.Name)
		res["Name"] = utils.FirstUpper(entity.Name)
		res["Description"] = entity.Description
		res["Properties"] = entity.Properties
		res["Commands"] = entity.GetCommands()
		res["PluralName"] = entity.PluralName()
		res["Events"] = entity.EntityEvents()
		res["EntityPluralName"] = entity.PluralName()
		res["AggregatePath"] = b.Aggregate.PluralName() + "/"
		res["AllViewProperties"] = b.GetAllViewProperties(entity)
		res["aggregateId"] = fmt.Sprintf("{%vId}", entity.Aggregate.FirstLowerName())
	}

	return res
}

func (b *BaseBuild) GetAllViewProperties(entity *config.Entity) *config.Properties {
	allProperties := config.NewProperties(b.Aggregate, b.Aggregate.Config.GetDefaultViewProperties(), nil)
	if entity != nil {
		allProperties.Adds(&entity.Properties)
	} else {
		allProperties.Adds(&b.Aggregate.Properties)
	}
	return allProperties
}

func (b *BaseBuild) ValuesOfEvent(event *config.Event) map[string]interface{} {
	res := b.Values()
	res["IsEntity"] = false
	res["IsAggregate"] = true
	if event != nil {
		res["Name"] = event.FirstUpperName()
		res["name"] = event.FirstLowerName()
		res["Version"] = strings.ToLower(event.Version)
		res["Properties"] = event.Properties
		res["Package"] = fmt.Sprintf("%s_event", b.Aggregate.SnakeName())
		res["FieldPackage"] = fmt.Sprintf("%s_field", b.Aggregate.SnakeName())
		res["Aggregate"] = b.Aggregate
		res["AggregateName"] = b.Aggregate.Name
		res["ServiceName"] = b.Config.Configuration.ServiceName
		res["EventType"] = event.EventType
		res["EventName"] = event.Name
		res["Event"] = event
		res["HasDataProperty"] = event.HasDataProperty()
		res["Description"] = event.Description
		res["IsAggregate"] = event.IsAggregate()
		res["IsEntity"] = !event.IsAggregate()
	}
	return res
}

func (b *BaseBuild) AddTimePackageValue(values map[string]interface{}, ps *config.Properties) {
	if ps == nil || values == nil {
		return
	}
	str := `"time"`
	if ps.HasTimeType() || ps.HasDateTimeType() {
		values["time"] = str
		return
	}
	if _, ok := values["time"]; !ok {
		values["time"] = ""
	}

}

func (b *BaseBuild) NewFileBuild(tmplFile, outFile string, values map[string]interface{}) *BuildAnyFile {
	return NewBuildAnyFile(*b, values, "static/tmpl/go/init"+tmplFile, outFile)
}

func (b *BaseBuild) DoBuild() error {
	if b.builds == nil {
		return nil
	}
	for _, build := range b.builds {
		println("building: " + build.GetOutFile())
		if err := build.Build(); err != nil {
			return err
		}
	}
	return nil
}

func (b *BaseBuild) GetOutFile() string {
	return b.OutFile
}

func (b *BaseBuild) Mkdir(dirs ...string) {
	for _, dirName := range dirs {
		_ = os.MkdirAll(dirName, os.ModePerm)
	}
}

func (o *AddBuildOptions) SetOverwrite(v *bool) *AddBuildOptions {
	overwrite := v
	o.Overwrite = overwrite
	return o
}

func (o *AddBuildOptions) GetOverwrite() *bool {
	return o.Overwrite
}
